package com.lyb.hardware;

import com.lyb.Utils;
import com.lyb.constant.StatusConstant;
import com.lyb.constant.SystemConstant;
import com.lyb.hardware.cpu.AlpacaCpu;
import com.lyb.hardware.cpu.AlpacaRegister;
import com.lyb.hardware.disk.AlpacaDisk;
import com.lyb.hardware.disk.AlpacaFile;
import com.lyb.hardware.io.A;
import com.lyb.hardware.io.AlpacaDMA;
import com.lyb.hardware.io.B;
import com.lyb.hardware.io.C;
import com.lyb.hardware.memory.AlpacaMemory;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

/**
 * 主板类
 *
 * @author Dayun Mr.Alpaca potato
 * @version 1.4.0
 */
public class AlpacaMainBoard {
    /**
     * 硬件
     */
    private AlpacaCpu cpu;
    private AlpacaMemory memory;
    private AlpacaDMA dma;
    private AlpacaDisk disk;

    /**
     * 队列
     */
    private static Queue<AlpacaPCB> emptyQueue;
    private static Queue<AlpacaPCB> readyQueue;
    private static Queue<AlpacaPCB> blockQueue;


    /* ********** 加载、启动 ********** */
    public AlpacaMainBoard() {
        /* 加载 */
        loadHardware();
        loadQueue();

        /* 启动 */
        powerOn();
    }

    /**
     * 加载硬件
     * 用于在启动时创建各部件
     */
    private void loadHardware() {
        cpu = new AlpacaCpu();
        memory = new AlpacaMemory();
        disk = new AlpacaDisk();
        dma = new AlpacaDMA();
    }

    /**
     * 加载队列
     * 用于在启动时加载初始队列
     */
    private void loadQueue() {
        /* 加载队列 */
        emptyQueue = new LinkedList<>(memory.getPcba());
        readyQueue = new LinkedList<>();
        blockQueue = new LinkedList<>();
    }

    /**
     * 启动各部件线程
     */
    private void powerOn() {
        new Thread(cpu, "CPU").start();
        new Thread(dma, "DMA").start();
    }

    /**
     * 磁盘格式化
     *
     * @return 操作结果
     */
    public boolean initDisk() {
        /* 删除原磁盘文件 */
        if (new File(SystemConstant.DISK_PATH).delete()) {
            /* 重建磁盘 */
            disk = new AlpacaDisk();
            return true;
        }
        return false;
    }

    /* ********** 进程方法 ********** */

    /**
     * 创建进程
     * 1.从空白队列中申请空白PCB
     * 2.申请内存空间
     * 3.加载PCB
     * 4.将文件写入内存
     * 5.将PCB装载进就绪队列
     *
     * @param file 将要创建运行进程的可执行文件
     * @return 进程创建结果
     */
    public boolean create(AlpacaFile file) {
        /* 1.申请空白PCB */
        AlpacaPCB pcb = emptyQueue.poll();
        /* 若无空白PCB直接返回错误 */
        if (pcb == null) {
            return false;
        }

        /* 2.根据可执行文件大小申请内存空间 */
        /* 计算可执行文件内容所需内存行数 */
        int size = file.readContent().split("\n").length;
        int[] indexs = memory.getEmptyArea(size);
        /* 若申请不到空间直接返回错误 */
        if (indexs == null) {
            emptyQueue.add(pcb);
            return false;
        }

        /* 3.写入PCB */
        pcb.setStartIndex(indexs[0]);
        pcb.setEndIndex(indexs[1]);
        /* PC初始指向文件起始索引 */
        pcb.load().setPc(indexs[0]);
        /* 文件路径 */
        pcb.setPath(file.getFilePath());
        /* 进程为可运行状态 */
        pcb.load().setPsw(StatusConstant.NORMAL);

        /* 4.将文件写入内存 */
        memory.write(pcb.getStartIndex(), pcb.getEndIndex(), file.readContent());

        /* 5.将PCB装载进就绪队列 */
        readyQueue.add(pcb);
        return true;
    }

    /**
     * 撤销进程
     * 1.输出进程执行结果
     * 2.释放进程所占内存空间
     * 3.初始化PCB并放回空白队列
     */
    public void destroy() {
        /* 获取CPU现场和运行进程 */
        AlpacaRegister context = cpu.getRegister();
        AlpacaPCB currentPcb = cpu.getCurrentPcb();

        /* 1.若数据寄存器不为空则输出执行结果 */
        if (!context.getDr().isEmpty()) {
            outputResult(currentPcb.getPath(), context.getDr());
        }

        /* 2.释放进程所占内存空间 */
        memory.clear(currentPcb.getStartIndex(), currentPcb.getEndIndex());

        /* 3.初始化PCB并放回空白队列 */
        currentPcb.init();
        emptyQueue.add(currentPcb);
    }

    /**
     * 阻塞进程
     * 1.保存现场
     * 2.修改进程状态
     * 3.将进程写入阻塞队列
     */
    public void block() {
        /* 获取CPU现场和运行进程 */
        AlpacaRegister context = cpu.getRegister();
        AlpacaPCB currentPcb = cpu.getCurrentPcb();
        /* 阻塞 */
        currentPcb.save(context);
        currentPcb.load().setPsw(StatusConstant.IO_BLOCK);
        blockQueue.add(currentPcb);
    }

    /**
     * 唤醒进程
     * 1.修改程序状态字
     * 2.将进程放回就绪队列
     *
     * @param pcb 将要放回就绪队列的进程
     */
    public void awake(AlpacaPCB pcb) {
        /* 1.修改程序状态字 */
        if (pcb.getRow() > pcb.getEndIndex() - pcb.getStartIndex() + 1) {
            pcb.load().setPsw(SystemConstant.END);
        } else {
            pcb.load().setPsw(StatusConstant.NORMAL);
        }

        /* 2.将进程放回就绪队列 */
        readyQueue.add(pcb);
    }


    /* ********** 输出执行结果 ********** */

    /**
     * 向系统指定文件路径输出结果，示例：{
     * 可执行文件路径：usr/example.e
     * 运行结果：x=12
     * }
     *
     * @param path 可执行文件的路径
     * @param ir   数据寄存器
     */
    private void outputResult(String path, Map<String, Double> ir) {
        try (FileWriter writer = new FileWriter(new File(SystemConstant.SYSTEM_OUTPUT_PATH), true)) {
            /* 输出文件的执行结果 */
            writer.write("可执行文件路径: " + path + "\n");
            writer.write("运行结果为:" + "\n");

            /* 遍历Map输出各变量值 */
            Set<String> keySet = ir.keySet();
            for (String key : keySet) {
                /* 输出各变量值 */
                writer.write("\t" + key + "=" + ir.get(key) + "\n");
            }
            writer.write("执行时间: " + Utils.getCurrentTime() + "\n");
            writer.write("----------------------------" + "\n");
        } catch (IOException e) {
            Utils.outCommandResult("[系统]: 系统指定文件路径出错");
            Utils.outCommandOver();
        }
    }


    /* ********** 内存方法 ********** */

    /**
     * 从内存中读取指定索引内容
     *
     * @param index 指定索引
     * @return 指定索引的内容(字符串格式)
     */
    public String readContentFromMemory(int index) {
        return memory.readContent(index);
    }

    /**
     * 获取内存空间分配表
     *
     * @return 内存空间分配表
     */
    public byte[] getMatFromMemory() {
        return memory.getMat();
    }

    /**
     * 获取当前内存空间空闲区域大小
     *
     * @return 空闲区域大小
     */
    public int getEmptySizeFromMemory() {
        return memory.getEmptySize();
    }


    /* ********** 文件方法 ********** */

    /**
     * 获取当前磁盘空闲空间大小
     *
     * @return 字节
     */
    public int getEmptySizeFromDisk() {
        return disk.getFreeLength();
    }

    /**
     * 获取当前磁盘的文件分配表
     *
     * @return 文件分配表
     */
    public byte[] getFatFromDisk() {
        return disk.getFat();
    }

    /**
     * 从磁盘中获取根目录项
     *
     * @return 根目录项的字节数组
     */
    public byte[] getRootDirectoryFromDisk() {
        return disk.getRootDirectory();
    }

    /**
     * 读取指定磁盘指定磁块内容并以字节数组形式返回
     *
     * @param diskNum 指定磁块号
     * @return 字节数组(64字节)，储存一块磁块字节内容
     * @throws IOException 异常，表示磁盘读取失败
     */
    public byte[] readBlockFromDisk(byte diskNum) throws IOException {
        return disk.readBlock(diskNum);
    }

    /**
     * 读取磁盘指定起始磁块及其关联磁块的内容并以字节数组形式返回
     *
     * @param startDiskNum 起始磁盘号
     * @return 字节数组，储存多块磁块字节内容
     * @throws IOException 异常，表示磁盘读取失败
     */
    public byte[] readBlocksFromDisk(byte startDiskNum) throws IOException {
        return disk.readBlocks(startDiskNum);
    }

    /**
     * 将发生改变的文件分配表写入磁盘文件
     */
    public void writeFatToDisk() {
        disk.writeFat();
    }

    /**
     * 将二进制文件内容写入指定磁块，返回操作结果
     *
     * @param diskNum 指定磁盘索引
     * @param content 文件内容(小于等于64字节)
     * @return 布尔值，true表示写入成功
     * @throws IOException 异常，表示磁块写入失败
     */
    public boolean writeBlockToDisk(byte diskNum, byte[] content) throws IOException {
        return disk.writeBlock(diskNum, content);
    }

    /**
     * 将二进制文件内容写入磁盘连续磁块，返回起始磁块号
     *
     * @param content 文件内容(64字节的整数倍)
     * @return byte类型，表示起始磁盘号，若为-1表示写入失败
     * @throws IOException 异常，表示磁块写入失败
     */
    public byte writeBlocksToDisk(byte[] content) throws IOException {
        return disk.writeBlocks(content);
    }

    /**
     * 清空磁盘文件指定关联磁块内容(写入空字节)
     *
     * @param startDiskNum 起始磁块号
     * @return 布尔值，true表示清空成功
     */
    public boolean clearBlocksToDisk(byte startDiskNum) {
        return disk.clearBlocks(startDiskNum);
    }

    /**
     * 从磁盘中获取一个空的磁块号
     * 查找不到返回 -1
     *
     * @return byte类型，表示空闲磁块号
     */
    public byte getFreeBlockNumFromDisk() {
        return disk.getFreeBlockNum();
    }

    /**
     * 将指定的磁块占用(文件分配表对应索引置为-1)
     * 通常用于创建目录时占用磁块(磁块内并无内容)
     */
    public void occupyBlockFromDisk(byte diskNum) {
        disk.occupyBlock(diskNum);
    }

    /**
     * 将指定的磁块释放(文件分配表对应索引置为0)
     * 通常用于删除空目录时释放磁块(磁块内并无内容)
     */
    public void freeBlockFromDisk(byte diskNum) {
        disk.freeBlock(diskNum);
    }


    /* ********** DMA方法 ********** */

    /**
     * 从DMA中获取当前可用设备A的实体队列
     *
     * @return 实体队列
     */
    public Queue<A> getaQueueFromDma() {
        return dma.getaQueue();
    }

    /**
     * 从DMA中获取当前可用设备B的实体队列
     *
     * @return 实体队列
     */
    public Queue<B> getbQueueFromDma() {
        return dma.getbQueue();
    }

    /**
     * 从DMA中获取当前可用设备C的实体队列
     *
     * @return 实体队列
     */
    public Queue<C> getcQueueFromDma() {
        return dma.getcQueue();
    }


    /* ********** Getter & Setter ********** */

    public synchronized Queue<AlpacaPCB> getReadyQueue() {
        return readyQueue;
    }

    public synchronized Queue<AlpacaPCB> getBlockQueue() {
        return blockQueue;
    }

    public synchronized Queue<AlpacaPCB> getEmptyQueue() {
        return emptyQueue;
    }

    public synchronized AlpacaPCB getCurrentPcb() {
        return this.cpu.getCurrentPcb();
    }

    /**
     * 获取当前cpu现场(核心寄存器)
     *
     * @return cpu现场
     */
    public synchronized AlpacaRegister getRegisterFromCpu() {
        return cpu.getRegister();
    }

    /**
     * 获取当前cpu中进程的时间片
     *
     * @return 时间片
     */
    public synchronized int getTimeSliceFromCpu() {
        return cpu.getTimeSlice();
    }

    /**
     * 设置当前CPU执行进程的状态字
     *
     * @param psw 程序状态字
     */
    public void setCpuCurrentPcbPsw(String psw) {
        this.cpu.getCurrentPcb().load().setPsw(psw);
    }

    /**
     * 设置当前CPU执行进程的阻塞原因
     *
     * @param description 阻塞原因
     */
    public void setCpuCurrentPcbDescription(String description) {
        this.cpu.getCurrentPcb().setDescription(description);
    }
}
